﻿using System;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;
using NetTopologySuite.Utilities;

namespace NetTopologySuite.Algorithm
{
	public class MinimumBoundingCircle
	{
		public MinimumBoundingCircle(IGeometry geom)
		{
			this._input = geom;
		}

		public IGeometry GetCircle()
		{
			this.Compute();
			if (this._centre == null)
			{
				return this._input.Factory.CreatePolygon(null, null);
			}
			IPoint point = this._input.Factory.CreatePoint(this._centre);
			if (this._radius == 0.0)
			{
				return point;
			}
			return point.Buffer(this._radius);
		}

		public IGeometry GetFarthestPoints()
		{
			this.Compute();
			int num = this._extremalPts.Length;
			if (num == 0)
			{
                return this._input.Factory.CreateLineString((ICoordinateSequence)null);
			}
			if (num != 1)
			{
				Coordinate coordinate = this._extremalPts[0];
				Coordinate coordinate2 = this._extremalPts[this._extremalPts.Length - 1];
				return this._input.Factory.CreateLineString(new Coordinate[]
				{
					coordinate,
					coordinate2
				});
			}
			return this._input.Factory.CreatePoint(this._centre);
		}

		public IGeometry GetDiameter()
		{
			this.Compute();
			int num = this._extremalPts.Length;
			if (num == 0)
			{
                return this._input.Factory.CreateLineString((ICoordinateSequence)null);
			}
			if (num != 1)
			{
				Coordinate coordinate = this._extremalPts[0];
				Coordinate coordinate2 = this._extremalPts[1];
				return this._input.Factory.CreateLineString(new Coordinate[]
				{
					coordinate,
					coordinate2
				});
			}
			return this._input.Factory.CreatePoint(this._centre);
		}

		public Coordinate[] GetExtremalPoints()
		{
			this.Compute();
			return this._extremalPts;
		}

		public Coordinate GetCentre()
		{
			this.Compute();
			return this._centre;
		}

		public double GetRadius()
		{
			this.Compute();
			return this._radius;
		}

		private void ComputeCentre()
		{
			switch (this._extremalPts.Length)
			{
			case 0:
				this._centre = null;
				return;
			case 1:
				this._centre = this._extremalPts[0];
				return;
			case 2:
				this._centre = new Coordinate((this._extremalPts[0].X + this._extremalPts[1].X) / 2.0, (this._extremalPts[0].Y + this._extremalPts[1].Y) / 2.0);
				return;
			case 3:
				this._centre = Triangle.Circumcentre(this._extremalPts[0], this._extremalPts[1], this._extremalPts[2]);
				return;
			default:
				return;
			}
		}

		private void Compute()
		{
			if (this._extremalPts != null)
			{
				return;
			}
			this.ComputeCirclePoints();
			this.ComputeCentre();
			if (this._centre != null)
			{
				this._radius = this._centre.Distance(this._extremalPts[0]);
			}
		}

		private void ComputeCirclePoints()
		{
			if (this._input.IsEmpty)
			{
				this._extremalPts = new Coordinate[0];
				return;
			}
			Coordinate[] array;
			if (this._input.NumPoints == 1)
			{
				array = this._input.Coordinates;
				this._extremalPts = new Coordinate[]
				{
					new Coordinate(array[0])
				};
				return;
			}
			Coordinate[] coordinates = this._input.ConvexHull().Coordinates;
			array = coordinates;
			if (coordinates[0].Equals2D(coordinates[coordinates.Length - 1]))
			{
				array = new Coordinate[coordinates.Length - 1];
				CoordinateArrays.CopyDeep(coordinates, 0, array, 0, coordinates.Length - 1);
			}
			if (array.Length <= 2)
			{
				this._extremalPts = CoordinateArrays.CopyDeep(array);
				return;
			}
			Coordinate coordinate = MinimumBoundingCircle.LowestPoint(array);
			Coordinate coordinate2 = MinimumBoundingCircle.PointWitMinAngleWithX(array, coordinate);
			for (int i = 0; i < array.Length; i++)
			{
				Coordinate coordinate3 = MinimumBoundingCircle.PointWithMinAngleWithSegment(array, coordinate, coordinate2);
				if (AngleUtility.IsObtuse(coordinate, coordinate3, coordinate2))
				{
					this._extremalPts = new Coordinate[]
					{
						new Coordinate(coordinate),
						new Coordinate(coordinate2)
					};
					return;
				}
				if (AngleUtility.IsObtuse(coordinate3, coordinate, coordinate2))
				{
					coordinate = coordinate3;
				}
				else
				{
					if (!AngleUtility.IsObtuse(coordinate3, coordinate2, coordinate))
					{
						this._extremalPts = new Coordinate[]
						{
							new Coordinate(coordinate),
							new Coordinate(coordinate2),
							new Coordinate(coordinate3)
						};
						return;
					}
					coordinate2 = coordinate3;
				}
			}
			Assert.ShouldNeverReachHere("Logic failure in Minimum Bounding Circle algorithm!");
		}

		private static Coordinate LowestPoint(Coordinate[] pts)
		{
			Coordinate coordinate = pts[0];
			for (int i = 1; i < pts.Length; i++)
			{
				if (pts[i].Y < coordinate.Y)
				{
					coordinate = pts[i];
				}
			}
			return coordinate;
		}

		private static Coordinate PointWitMinAngleWithX(Coordinate[] pts, Coordinate P)
		{
			double num = double.MaxValue;
			Coordinate result = null;
			foreach (Coordinate coordinate in pts)
			{
				if (coordinate != P)
				{
					double num2 = coordinate.X - P.X;
					double num3 = coordinate.Y - P.Y;
					if (num3 < 0.0)
					{
						num3 = -num3;
					}
					double num4 = System.Math.Sqrt(num2 * num2 + num3 * num3);
					double num5 = num3 / num4;
					if (num5 < num)
					{
						num = num5;
						result = coordinate;
					}
				}
			}
			return result;
		}

		private static Coordinate PointWithMinAngleWithSegment(Coordinate[] pts, Coordinate P, Coordinate Q)
		{
			double num = double.MaxValue;
			Coordinate result = null;
			foreach (Coordinate coordinate in pts)
			{
				if (coordinate != P && coordinate != Q)
				{
					double num2 = AngleUtility.AngleBetween(P, coordinate, Q);
					if (num2 < num)
					{
						num = num2;
						result = coordinate;
					}
				}
			}
			return result;
		}

		private readonly IGeometry _input;

		private Coordinate[] _extremalPts;

		private Coordinate _centre;

		private double _radius;
	}
}
